package lol.clann.minecraft.springboot.api.bukkit.rawmessage;

import relocate.com.fasterxml.jackson.databind.node.ArrayNode;
import relocate.com.fasterxml.jackson.databind.node.JsonNodeFactory;
import relocate.com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import lol.clann.minecraft.springboot.api.bukkit.utils.PacketUtils;
import lol.clann.minecraft.springboot.api.bukkit.utils.PlayerUtils;
import lol.clann.minecraft.springboot.api.context.SpringContext;
import lol.clann.minecraft.springboot.api.model.ClickCallback;
import lol.clann.minecraft.springboot.api.model.LazyField;
import lol.clann.minecraft.springboot.api.service.ClickCallbackService;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;

/**
 * @author pyz
 * @date 2018/8/18 上午10:09
 */
public class RawMessage implements Comparable<RawMessage> {

    private final List<MessagePart> parts = new ArrayList<>();
    private final List<ClickCallback> callbacks = new ArrayList<>();
    private static final LazyField<PlayerUtils> playerUtils = LazyField.of(() -> SpringContext.getBean(PlayerUtils.class));
    private static final LazyField<PacketUtils> packetUtils = LazyField.of(() -> SpringContext.getBean(PacketUtils.class));
    private static final LazyField<ClickCallbackService> clickCallbackService = LazyField.of(() -> SpringContext.getBean(ClickCallbackService.class));

    static final BiMap<ChatColor, String> stylesToNames;

    static {
        ImmutableBiMap.Builder<ChatColor, String> builder = ImmutableBiMap.builder();
        for (final ChatColor style : ChatColor.values()) {
            if (!style.isFormat()) {
                continue;
            }

            String styleName;
            switch (style) {
                case MAGIC:
                    styleName = "obfuscated";
                    break;
                case UNDERLINE:
                    styleName = "underlined";
                    break;
                default:
                    styleName = style.name().toLowerCase();
                    break;
            }

            builder.put(style, styleName);
        }
        stylesToNames = builder.build();
    }

    /**
     * Creates a new {@link RawMessage} object
     *
     * @param text The text to start with
     */
    RawMessage(String text) {
        parts.add(new MessagePart(text));
    }

    /**
     * Creates a new {@link RawMessage} object
     *
     * @param text The text to start with
     * @return A new {@link RawMessage} object
     */
    public static RawMessage createRawMessage(String text) {
        return new RawMessage(text);
    }

    /**
     * Creates a new {@link RawMessage} object
     *
     * @return A new {@link RawMessage} object
     */
    public static RawMessage createRawMessage() {
        return createRawMessage("");
    }

    /**
     * @return The latest {@link MessagePart}
     * @throws ArrayIndexOutOfBoundsException If {@code parts.size() <= 0}.
     */
    public MessagePart last() {
        if (parts.size() <= 0) {
            throw new ArrayIndexOutOfBoundsException("No MessageParts exist!");
        }
        return parts.get(parts.size() - 1);
    }

    /**
     * Converts this {@link RawMessage} instance to actual JSON
     *
     * @return The JSON representation of this {@link RawMessage}
     */
    public ObjectNode toJSON() {
        ObjectNode obj = JsonNodeFactory.instance.objectNode();
        obj.put("text", "");
        ArrayNode array = JsonNodeFactory.instance.arrayNode();
        parts.stream()
                .map(MessagePart::toJSON)
                .forEach(array::add);

        obj.put("extra", array);
        return obj;
    }

    /**
     * Converts this {@link RawMessage} object to a String representation of the JSON.
     * This is an alias of {@code toJSON().toString()}.
     */
    @Override
    public String toString() {
        return toJSON().toString();
    }

    /**
     * Converts this {@link RawMessage} object to the legacy formatting system, which
     * uses formatting codes (like &amp;6, &amp;l, &amp;4, etc.)
     *
     * @return This {@link RawMessage} instance {@link RawMessage} in legacy format
     */
    public String toLegacy() {
        StringBuilder output = new StringBuilder();

        parts.stream()
                .map(MessagePart::toLegacy)
                .forEach(output::append);

        return output.toString();
    }

    private void logCallbackPlayer(String name) {
        for (MessagePart part : this.parts) {
            MessageEvent onClick = part.getOnClick();
            if (onClick != null && onClick.getClickCallback() != null) {
                onClick.getClickCallback().getPlayers().add(name);
            }
        }
    }

    private void enableCallbacks() {
        for (MessagePart part : this.parts) {
            MessageEvent onClick = part.getOnClick();
            if (onClick != null && onClick.getClickCallback() != null) {
                clickCallbackService.get().addCallback(onClick.getClickCallback());
            }
        }
    }

    /**
     * Sends this {@link RawMessage} to all the players specified
     *
     * @param senders The players you want to send this to
     */
    public <T extends CommandSender> void send(T... senders) {
        send(Arrays.asList(senders));
    }

    public <T extends CommandSender> void send(Collection<T> senders) {
        Object packet = packetUtils.get().newPacketPlayOutChat(toString());
        String normalMessage = null;
        int playerCount = 0;
        for (CommandSender sender : senders) {
            if (sender instanceof Player) {
                playerUtils.get().sendPacket((Player) sender, packet);
                logCallbackPlayer(sender.getName());
                playerCount++;
            } else {
                if (normalMessage == null) {
                    normalMessage = this.toLegacy();
                }
                sender.sendMessage(normalMessage);
            }
        }
        if (playerCount > 0) {
            enableCallbacks();
        }
    }

    public void broadcast() {// broadcast
        Collection<? extends Player> players = Bukkit.getServer().getOnlinePlayers();
        send((Collection) players);
        send(Bukkit.getConsoleSender());
    }

    /**
     * Sets the color of the current message part.
     *
     * @param color The color to set
     * @return This {@link RawMessage} instance
     */
    public RawMessage color(ChatColor color) {
        last().setColor(color);
        return this;
    }

    /**
     * Adds a style to the current message part.
     *
     * @param style The style to add
     * @return This {@link RawMessage} instance
     */
    public RawMessage style(ChatColor style) {
        last().addStyle(style);
        return this;
    }

    /**
     * 点击执行回调
     *
     * @param timeout 回调超时时间(ms)
     * @param callback
     * @return
     */
    private RawMessage executeCallback(long timeout, boolean async, Consumer<Player> callback) {
        if (timeout <= 0) {
            timeout = -1;
        }
        ClickCallback clickCallback = ClickCallback.builder()
                .callback(callback)
                .timeout(timeout)
                .async(async)
                .nonPlayer()
                .build();
        MessageEvent event = ClickEvent.runCommand("/springboot executeCallback " + clickCallback.getKey());
        event.setClickCallback(clickCallback);
        last().setOnClick(event);
        return this;
    }

    /**
     * 点击执行回调
     *
     * @param callback
     * @return
     */
    public RawMessage executeCallback(Consumer<Player> callback) {
        return executeCallback(-1, false, callback);
    }

    /**
     * 点击执行回调
     *
     * @param callback
     * @return
     */
    public RawMessage executeCallbackAsync(Consumer<Player> callback) {
        return executeCallback(-1, true, callback);
    }

    /**
     * 点击执行回调
     *
     * @param callback
     * @return
     */
    public RawMessage executeCallback(long timeout, Consumer<Player> callback) {
        return executeCallback(timeout, false, callback);
    }

    /**
     * 点击执行回调
     *
     * @param callback
     * @return
     */
    public RawMessage executeCallbackAsync(long timeout, Consumer<Player> callback) {
        return executeCallback(timeout, true, callback);
    }

    /**
     * Makes the text run a command.
     *
     * @param command The command to run
     * @return This {@link RawMessage} instance
     */
    public RawMessage runCommand(String command) {
        last().setOnClick(ClickEvent.runCommand(command));
        return this;
    }

    /**
     * Makes the text suggest a command.
     *
     * @param command The command to suggest
     * @return This {@link RawMessage} instance
     */
    public RawMessage suggestCommand(String command) {
        last().setOnClick(ClickEvent.suggestCommand(command));
        return this;
    }

    /**
     * Opens a URL.
     *
     * @param url The url to open
     * @return This {@link RawMessage} instance
     */
    public RawMessage openURL(String url) {
        last().setOnClick(ClickEvent.openURL(url));
        return this;
    }

    /**
     * Shows text when you hover over it
     *
     * @param text The text to show
     * @return This {@link RawMessage} instance
     */
    public RawMessage tooltip(String text) {
        last().setOnHover(HoverEvent.showText(text));
        return this;
    }


    /**
     * Shows text when you hover over it
     *
     * @param message The text to show
     * @return This {@link RawMessage} instance
     */
    public RawMessage tooltip(RawMessage message) {
        last().setOnHover(HoverEvent.showText(message));
        return this;
    }

    public RawMessage showItem(ItemStack itemStack) {
        last().setOnHover(HoverEvent.showItemStack(itemStack));
        return this;
    }

    /**
     * Shows an achievement when you hover over it
     *
     * @param id The id of the achievement
     * @return This {@link RawMessage} instance
     */
    public RawMessage achievement(String id) {
        last().setOnHover(HoverEvent.showAchievement(id));
        return this;
    }

    /**
     * Adds another part to this {@link RawMessage}
     *
     * @param text The text to start the next {@link MessagePart} with
     * @return This {@link RawMessage} instance
     */
    public RawMessage then(String text) {
        return then(new MessagePart(text));
    }

    /**
     * Adds another part to this {@link RawMessage}
     *
     * @param nextPart The next {@link MessagePart}
     * @return This {@link RawMessage} instance
     */
    public RawMessage then(MessagePart nextPart) {
        parts.add(nextPart);
        return this;
    }

    /**
     * Adds a blank line to the message
     *
     * @return This {@link RawMessage} instance
     */
    public RawMessage newline() {
        return then("\n");
    }

    /**
     * 清空
     *
     * @return
     */
    public RawMessage clear() {
        parts.clear();
        return this;
    }

    /**
     * 移除最后一个part
     *
     * @return
     */
    public RawMessage popLast() {
        if (parts.size() > 0) {
            parts.remove(parts.size() - 1);
        }
        return this;
    }

    @Override
    public int compareTo(@NotNull RawMessage o) {
        int index = 0;
        while (true) {
            if (index >= parts.size()) {
                if (index >= o.parts.size()) {
                    return 0;
                } else {
                    return -1;
                }
            } else {
                if (index >= o.parts.size()) {
                    return 1;
                } else {
                    MessagePart left = parts.get(index);
                    MessagePart right = o.parts.get(index);
                    int r = left.compareTo(right);
                    if (r != 0) {
                        return r;
                    }
                }
            }
            index++;
        }
    }
}
